"""
Fortran parser for CodeMCP framework.

This module provides comprehensive parsing of Fortran source code,
supporting both modern and legacy Fortran constructs.
"""

import logging
from pathlib import Path
from typing import Dict, List, Any, Optional, Set, Union

try:
    from ...core.error_handler import AnalysisError, log_info
    from ...parsers.fortran.ast_builder import FortranASTBuilder, FortranASTNode
    from ...parsers.fortran.lexer import FortranLexer, FortranFormat
except ImportError:
    # Fallback for direct execution
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).parent.parent.parent))
    from core.error_handler import AnalysisError, log_info
    from parsers.fortran.ast_builder import FortranASTBuilder, FortranASTNode  
    from parsers.fortran.lexer import FortranLexer, FortranFormat


class FortranParser:
    """
    Fortran source code parser.
    
    Provides comprehensive parsing of Fortran programs, modules, and procedures
    with support for both modern (90+) and legacy (77) Fortran constructs.
    """
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self.ast_builder = FortranASTBuilder()
        self.lexer = FortranLexer()
        self._reset()
    
    def _reset(self):
        """Reset parser state."""
        self.current_file = ""
        self.source_lines = []
        self.format = FortranFormat.UNKNOWN
        self.parse_tree = None
    
    def parse_file(self, file_path: str) -> Dict[str, Any]:
        """
        Parse a Fortran source file.
        
        Args:
            file_path: Path to Fortran source file
            
        Returns:
            Dictionary containing parsed information
            
        Raises:
            AnalysisError: If parsing fails
        """
        try:
            file_path_obj = Path(file_path)
            if not file_path_obj.exists():
                raise AnalysisError(f"File not found: {file_path}")
            
            self.current_file = str(file_path_obj)
            
            with open(file_path_obj, 'r', encoding='utf-8') as f:
                source = f.read()
            
            return self.parse_source(source, self.current_file)
            
        except Exception as e:
            self.logger.error(f"Error parsing file {file_path}: {e}")
            raise AnalysisError(f"Failed to parse {file_path}: {e}")
    
    def parse_source(self, source: str, filename: str = "<string>") -> Dict[str, Any]:
        """
        Parse Fortran source code.
        
        Args:
            source: Fortran source code
            filename: Source filename for error reporting
            
        Returns:
            Dictionary containing parsed information
        """
        try:
            self._reset()
            self.current_file = filename
            self.source_lines = source.split('\n')
            self.format = self.lexer.detect_format(source)
            
            log_info(f"Parsing {filename} (format: {self.format.value})")
            
            # Build AST
            self.parse_tree = self.ast_builder.build_from_source(source, filename)
            
            # Extract structured information
            result = {
                'filename': filename,
                'format': self.format.value,
                'programs': self._extract_programs(),
                'modules': self._extract_modules(),
                'functions': self._extract_functions(),
                'subroutines': self._extract_subroutines(),
                'types': self._extract_types(),
                'interfaces': self._extract_interfaces(),
                'common_blocks': self._extract_common_blocks(),
                'uses': self._extract_uses(),
                'includes': self._extract_includes(),
                'variables': self._extract_variables(),
                'calls': self._extract_calls(),
                'statistics': self._compute_statistics()
            }
            
            log_info(f"Successfully parsed {filename}: "
                    f"{len(result['functions'])} functions, "
                    f"{len(result['subroutines'])} subroutines, "
                    f"{len(result['modules'])} modules")
            
            return result
            
        except Exception as e:
            self.logger.error(f"Error parsing source: {e}")
            raise AnalysisError(f"Failed to parse source: {e}")
    
    def _extract_programs(self) -> List[Dict[str, Any]]:
        """Extract program unit information."""
        programs = []
        if not self.parse_tree:
            return programs
        
        def collect_programs(node: FortranASTNode):
            if node.node_type in ['Program', 'Main_Program']:
                program_info = {
                    'name': node.attributes.get('name', 'unnamed'),
                    'line_number': node.line_number,
                    'contains': [],
                    'uses': []
                }
                
                # Collect contained procedures and use statements
                for child in node.children:
                    if child.node_type in ['Function', 'Subroutine']:
                        program_info['contains'].append(child.attributes.get('name', 'unnamed'))
                    elif child.node_type in ['Use', 'Use_Stmt']:
                        program_info['uses'].append(child.attributes.get('module', 'unknown'))
                
                programs.append(program_info)
            
            for child in node.children:
                collect_programs(child)
        
        collect_programs(self.parse_tree)
        return programs
    
    def _extract_modules(self) -> List[Dict[str, Any]]:
        """Extract module information."""
        return self.ast_builder.extract_modules(self.parse_tree) if self.parse_tree else []
    
    def _extract_functions(self) -> List[Dict[str, Any]]:
        """Extract function information."""
        functions = []
        procedures = self.ast_builder.extract_procedures(self.parse_tree) if self.parse_tree else []
        
        for proc in procedures:
            if proc['type'] == 'function':
                functions.append({
                    'name': proc['name'],
                    'line_number': proc['line_number'],
                    'calls': proc['calls'],
                    'return_type': 'unknown',  # Would need more sophisticated parsing
                    'arguments': [],  # Would need more sophisticated parsing
                    'is_recursive': False,  # Would need more sophisticated parsing
                    'is_pure': False,  # Would need more sophisticated parsing
                    'is_elemental': False  # Would need more sophisticated parsing
                })
        
        return functions
    
    def _extract_subroutines(self) -> List[Dict[str, Any]]:
        """Extract subroutine information."""
        subroutines = []
        procedures = self.ast_builder.extract_procedures(self.parse_tree) if self.parse_tree else []
        
        for proc in procedures:
            if proc['type'] == 'subroutine':
                subroutines.append({
                    'name': proc['name'],
                    'line_number': proc['line_number'],
                    'calls': proc['calls'],
                    'arguments': [],  # Would need more sophisticated parsing
                    'is_recursive': False,  # Would need more sophisticated parsing
                    'is_pure': False,  # Would need more sophisticated parsing
                    'is_elemental': False  # Would need more sophisticated parsing
                })
        
        return subroutines
    
    def _extract_types(self) -> List[Dict[str, Any]]:
        """Extract derived type information."""
        types = []
        if not self.parse_tree:
            return types
        
        def collect_types(node: FortranASTNode):
            if node.node_type in ['Type', 'Derived_Type_Def']:
                type_info = {
                    'name': node.attributes.get('name', 'unnamed'),
                    'line_number': node.line_number,
                    'components': [],
                    'procedures': [],
                    'is_abstract': False,
                    'extends': None,
                    'is_sequence': False,
                    'is_bind_c': False
                }
                types.append(type_info)
            
            for child in node.children:
                collect_types(child)
        
        collect_types(self.parse_tree)
        return types
    
    def _extract_interfaces(self) -> List[Dict[str, Any]]:
        """Extract interface block information."""
        interfaces = []
        if not self.parse_tree:
            return interfaces
        
        def collect_interfaces(node: FortranASTNode):
            if node.node_type in ['Interface', 'Interface_Block']:
                interface_info = {
                    'name': node.attributes.get('name', 'generic'),
                    'line_number': node.line_number,
                    'procedures': [],
                    'is_generic': True,
                    'is_operator': False,
                    'is_assignment': False
                }
                interfaces.append(interface_info)
            
            for child in node.children:
                collect_interfaces(child)
        
        collect_interfaces(self.parse_tree)
        return interfaces
    
    def _extract_common_blocks(self) -> List[Dict[str, Any]]:
        """Extract COMMON block information."""
        common_blocks = []
        
        # Simple pattern matching for COMMON blocks
        for i, line in enumerate(self.source_lines):
            line_clean = line.strip().upper()
            if line_clean.startswith('COMMON'):
                # Parse COMMON block (simplified)
                parts = line_clean.split()
                if len(parts) > 1:
                    name = 'blank'
                    variables = []
                    
                    # Look for named COMMON block
                    if '/' in line_clean:
                        # Extract name between slashes
                        start = line_clean.find('/') + 1
                        end = line_clean.find('/', start)
                        if end > start:
                            name = line_clean[start:end].strip()
                    
                    common_blocks.append({
                        'name': name,
                        'line_number': i + 1,
                        'variables': variables
                    })
        
        return common_blocks
    
    def _extract_uses(self) -> List[Dict[str, Any]]:
        """Extract USE statement information."""
        uses = []
        if not self.parse_tree:
            return uses
        
        def collect_uses(node: FortranASTNode):
            if node.node_type in ['Use', 'Use_Stmt']:
                use_info = {
                    'module': node.attributes.get('module', 'unknown'),
                    'line_number': node.line_number,
                    'only': [],  # Items imported with ONLY clause
                    'rename': {},  # Renamed items
                    'intrinsic': False,  # USE, INTRINSIC
                    'non_intrinsic': False  # USE, NON_INTRINSIC
                }
                uses.append(use_info)
            
            for child in node.children:
                collect_uses(child)
        
        collect_uses(self.parse_tree)
        return uses
    
    def _extract_includes(self) -> List[Dict[str, Any]]:
        """Extract INCLUDE statement information."""
        includes = []
        
        # Simple pattern matching for INCLUDE statements
        for i, line in enumerate(self.source_lines):
            line_clean = line.strip().upper()
            if line_clean.startswith('INCLUDE'):
                parts = line.split(None, 1)
                if len(parts) > 1:
                    filename = parts[1].strip('\'"')
                    includes.append({
                        'filename': filename,
                        'line_number': i + 1
                    })
        
        return includes
    
    def _extract_variables(self) -> List[Dict[str, Any]]:
        """Extract variable declaration information."""
        variables = []
        
        # This would need more sophisticated parsing
        # For now, return empty list
        return variables
    
    def _extract_calls(self) -> List[Dict[str, Any]]:
        """Extract procedure call information."""
        calls = []
        if not self.parse_tree:
            return calls
        
        def collect_calls(node: FortranASTNode):
            if node.node_type in ['Call', 'Call_Stmt']:
                call_info = {
                    'target': node.attributes.get('target', 'unknown'),
                    'line_number': node.line_number,
                    'arguments': [],  # Would need more sophisticated parsing
                    'in_procedure': 'unknown'  # Would need context tracking
                }
                calls.append(call_info)
            
            for child in node.children:
                collect_calls(child)
        
        collect_calls(self.parse_tree)
        return calls
    
    def _compute_statistics(self) -> Dict[str, Any]:
        """Compute parsing statistics."""
        if not self.parse_tree:
            return {}
        
        return {
            'total_lines': len(self.source_lines),
            'non_empty_lines': len([line for line in self.source_lines if line.strip()]),
            'comment_lines': len([line for line in self.source_lines 
                                if line.strip().startswith(('!', 'C', 'c', '*'))]),
            'format': self.format.value,
            'parse_success': True
        }
    
    def get_supported_extensions(self) -> List[str]:
        """Get list of supported Fortran file extensions."""
        return [
            '.f90', '.f95', '.f03', '.f08', '.f18',  # Free-form modern Fortran
            '.F90', '.F95', '.F03', '.F08', '.F18',  # Preprocessed modern Fortran
            '.f', '.for', '.ftn',                     # Fixed-form Fortran
            '.F', '.FOR', '.FTN'                      # Preprocessed fixed-form
        ]
    
    def is_fortran_file(self, file_path: str) -> bool:
        """Check if file has a Fortran extension."""
        return Path(file_path).suffix in self.get_supported_extensions()